#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
天翼云盘PC版基础客户端
包含登录、认证、签名等公共方法
"""

import requests
import hashlib
import hmac
import time
import uuid
import re
import json
import os
import base64
import xml.etree.ElementTree as ET
from typing import Dict, List, Optional, Any, Union
from urllib.parse import urlparse, parse_qs, urlencode, quote
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5, AES
from Crypto.Util.Padding import pad
import binascii
import logging

# 常量定义
ACCOUNT_TYPE = "02"
APP_ID = "8025431004"
CLIENT_TYPE = "10020"
VERSION = "6.2"

WEB_URL = "https://cloud.189.cn"
AUTH_URL = "https://open.e.189.cn"
API_URL = "https://api.cloud.189.cn"
UPLOAD_URL = "https://upload.cloud.189.cn"

RETURN_URL = "https://m.cloud.189.cn/zhuanti/2020/loginErrorPc/index.html"

PC = "TELEPC"
MAC = "TELEMAC"

CHANNEL_ID = "web_cloud.189.cn"

class BaseCloud189Client:
    """天翼云盘PC版基础客户端"""
    
    def __init__(self, cache_file: str = "data/db/cache_189.json", enable_debug: bool = True):
        self.session = requests.Session()
        # 设置基础请求头
        self.session.headers.update({
            'Accept': 'application/json;charset=UTF-8',
            'Referer': WEB_URL,
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        })
        
        self.cache_file = cache_file
        self.token_info = None
        self.login_param = None
        self.family_id = None
        self.identity = None
        self.enable_debug = enable_debug
        
        # 设置日志
        if enable_debug:
            logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
            self.logger = logging.getLogger(__name__)
        else:
            self.logger = logging.getLogger(__name__)
            self.logger.setLevel(logging.WARNING)
        
        # 加载缓存
        self.load_cache()
    
    def client_suffix(self) -> Dict[str, str]:
        """客户端后缀参数"""
        import random
        rand_num = random.randint(10000, 99999)
        rand_num2 = random.randint(1000000000, 9999999999)
        return {
            "clientType": PC,
            "version": VERSION,
            "channelId": CHANNEL_ID,
            "rand": f"{rand_num}_{rand_num2}"
        }
    
    def get_http_date_str(self) -> str:
        """获取HTTP规范时间"""
        return time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())
    
    def timestamp(self) -> int:
        """获取时间戳(毫秒)"""
        return int(time.time() * 1000)
    
    def get_timestamp(self) -> int:
        """获取时间戳(毫秒) - 兼容性方法"""
        return self.timestamp()
    
    def get_http_date(self) -> str:
        """获取HTTP日期格式 - 兼容性方法"""
        return self.get_http_date_str()
    
    def hmac_sha1(self, data: str, key: str) -> str:
        """HMAC-SHA1签名"""
        return hmac.new(key.encode(), data.encode(), hashlib.sha1).hexdigest()
    
    def signature_of_hmac(self, session_secret: str, session_key: str, operate: str, 
                         full_url: str, date_of_gmt: str, params: str = "") -> str:
        """HMAC签名"""
        url_path = re.search(r'://[^/]+((/[^/\s?#]+)*)', full_url).group(1)
        data = f"SessionKey={session_key}&Operate={operate}&RequestURI={url_path}&Date={date_of_gmt}"
        if params:
            data += f"&params={params}"
        
        signature = hmac.new(
            session_secret.encode(), 
            data.encode(), 
            hashlib.sha1
        ).hexdigest().upper()
        return signature
    
    def rsa_encrypt(self, public_key: str, orig_data: str) -> str:
        """RSA加密用户名密码"""
        try:
            # 处理公钥格式
            if not public_key.startswith('-----BEGIN'):
                public_key = f"-----BEGIN PUBLIC KEY-----\n{public_key}\n-----END PUBLIC KEY-----"
            
            rsa_key = RSA.import_key(public_key)
            cipher = PKCS1_v1_5.new(rsa_key)
            encrypted = cipher.encrypt(orig_data.encode())
            # 转换为大写十六进制字符串
            return binascii.hexlify(encrypted).decode().upper()
        except Exception as e:
            print(f"RSA加密失败: {e}")
            if self.enable_debug:
                self.logger.error(f"RSA加密异常: {e}")
            return ""
    
    def aes_ecb_encrypt(self, data: str, key: str) -> str:
        """AES ECB加密params"""
        try:
            cipher = AES.new(key[:16].encode(), AES.MODE_ECB)
            padded_data = pad(data.encode(), AES.block_size)
            encrypted = cipher.encrypt(padded_data)
            return binascii.hexlify(encrypted).decode().upper()
        except Exception as e:
            print(f"AES加密失败: {e}")
            return ""
    
    def signature_header(self, url: str, method: str, params: str = "", is_family: bool = False) -> Dict[str, str]:
        """生成签名头"""
        if not self.token_info:
            return {}
        
        date_of_gmt = self.get_http_date_str()
        session_key = self.token_info.get('sessionKey', '')
        session_secret = self.token_info.get('sessionSecret', '')
        
        if is_family:
            session_key = self.token_info.get('familySessionKey', '')
            session_secret = self.token_info.get('familySessionSecret', '')
        
        return {
            "Date": date_of_gmt,
            "SessionKey": session_key,
            "X-Request-ID": str(uuid.uuid4()),
            "Signature": self.signature_of_hmac(session_secret, session_key, method, url, date_of_gmt, params)
        }
    
    def encrypt_params(self, params: Dict[str, str], is_family: bool = False) -> str:
        """加密参数"""
        if not self.token_info or not params:
            return ""
        
        session_secret = self.token_info.get('sessionSecret', '')
        if is_family:
            session_secret = self.token_info.get('familySessionSecret', '')
        
        # 将参数编码为URL格式
        param_str = '&'.join([f"{k}={v}" for k, v in params.items()])
        return self.aes_ecb_encrypt(param_str, session_secret)
    
    def load_cache(self):
        """从缓存文件加载登录信息"""
        try:
            if os.path.exists(self.cache_file):
                with open(self.cache_file, 'r', encoding='utf-8') as f:
                    cache_data = json.load(f)
                
                # 检查缓存时间戳（缓存有效期7天）
                cache_timestamp = cache_data.get('timestamp', 0)
                current_time = int(time.time())
                cache_age_days = (current_time - cache_timestamp) / (24 * 3600)
                
                if cache_age_days > 7:
                    print(f"缓存已过期（{cache_age_days:.1f}天），清理缓存")
                    self.clear_cache()
                    return False
                
                self.token_info = cache_data.get('token_info')
                self.family_id = cache_data.get('family_id')
                self.identity = cache_data.get('identity')
                
                if self.token_info:
                    print(f"已从缓存加载登录信息: {self.token_info.get('loginName', 'Unknown')}")
                    return True
            else:
                print(f"缓存文件 {self.cache_file} 不存在")
        except Exception as e:
            print(f"加载缓存失败: {e}")
            self.clear_cache()
        return False
    
    def clear_cache(self):
        """清理缓存"""
        try:
            if os.path.exists(self.cache_file):
                os.remove(self.cache_file)
            self.token_info = None
            self.family_id = None
            self.identity = None
        except Exception as e:
            print(f"清理缓存失败: {e}")
    
    def save_cache(self):
        """保存缓存"""
        cache_data = {
            'token_info': self.token_info,
            'identity': self.identity,
            'family_id': self.family_id,
            'timestamp': int(time.time())
        }
        
        try:
            # 确保目录存在
            os.makedirs(os.path.dirname(self.cache_file), exist_ok=True)
            with open(self.cache_file, 'w', encoding='utf-8') as f:
                json.dump(cache_data, f, ensure_ascii=False, indent=2)
            print(f"登录信息已保存到 {self.cache_file}")
        except Exception as e:
            print(f"保存缓存失败: {e}")
    
    def is_login(self) -> bool:
        """检查是否已登录"""
        if self.token_info is None:
            return False
        
        # 通过调用getUserInfo接口验证登录状态
        try:
            url = f"{API_URL}/getUserInfo.action"
            params = self.client_suffix()
            headers = self.signature_header(url, "GET")
            response = self.session.get(url, params=params, headers=headers)
            return response.status_code == 200
        except Exception:
            return False
    
    def get_encrypt_conf(self) -> Optional[Dict[str, Any]]:
        """获取加密配置"""
        url = f"{AUTH_URL}/api/logbox/config/encryptConf.do"
        data = {'appId': APP_ID}
        headers = {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
        
        if self.enable_debug:
            self.logger.debug(f"请求URL: {url}")
            self.logger.debug(f"请求数据: {data}")
            self.logger.debug(f"请求头: {headers}")
        
        try:
            response = self.session.post(url, data=data, headers=headers)
            if self.enable_debug:
                self.logger.debug(f"响应状态码: {response.status_code}")
                self.logger.debug(f"响应内容: {response.text}")
            
            if response.status_code == 200:
                return response.json()
        except Exception as e:
            print(f"获取加密配置失败: {e}")
            if self.enable_debug:
                self.logger.error(f"获取加密配置异常: {e}")
        return None
    
    def get_login_param(self) -> bool:
        """获取登录参数"""
        # 清除cookie
        self.session.cookies.clear()
        
        # 获取登录页面参数
        login_url = f"{WEB_URL}/api/portal/unifyLoginForPC.action"
        params = {
            'appId': APP_ID,
            'clientType': CLIENT_TYPE,
            'returnURL': RETURN_URL,
            'timeStamp': str(self.timestamp())
        }
        
        if self.enable_debug:
            self.logger.debug(f"请求URL: {login_url}")
            self.logger.debug(f"请求参数: {params}")
        
        try:
            response = self.session.get(login_url, params=params)
            if self.enable_debug:
                self.logger.debug(f"响应状态码: {response.status_code}")
                self.logger.debug(f"响应内容: {response.text[:500]}...")  # 只显示前500字符
            
            if response.status_code == 200:
                content = response.text
                # 使用正则表达式提取参数
                captcha_token_match = re.search(r"'captchaToken' value='(.+?)'", content)
                lt_match = re.search(r'lt = "(.+?)"', content)
                param_id_match = re.search(r'paramId = "(.+?)"', content)
                req_id_match = re.search(r'reqId = "(.+?)"', content)
                
                if not all([captcha_token_match, lt_match, param_id_match, req_id_match]):
                    print("解析登录页面参数失败")
                    return False
                
                # 获取加密配置
                encrypt_conf = self.get_encrypt_conf()
                if not encrypt_conf or encrypt_conf.get('result') != 0:
                    print("获取加密配置失败")
                    return False
                
                pub_key = encrypt_conf['data']['pubKey']
                pre = encrypt_conf['data']['pre']
                
                # 构建完整的RSA公钥
                rsa_key = f"-----BEGIN PUBLIC KEY-----\n{pub_key}\n-----END PUBLIC KEY-----"
                
                self.login_param = {
                    'captchaToken': captcha_token_match.group(1),
                    'lt': lt_match.group(1),
                    'paramId': param_id_match.group(1),
                    'reqId': req_id_match.group(1),
                    'jRsaKey': rsa_key,
                    'pre': pre
                }
                return True
        except Exception as e:
            print(f"获取登录参数失败: {e}")
        return False
    
    def auto_login(self) -> bool:
        """自动登录（使用缓存的凭据）"""
        if not hasattr(self, '_cached_username') or not hasattr(self, '_cached_password'):
            return False
        
        print("尝试自动重新登录...")
        return self.login(self._cached_username, self._cached_password)
    
    def login(self, username: str = None, password: str = None, captcha: str = None) -> bool:
        """登录"""
        # 如果没有提供用户名密码，尝试从用户输入获取
        if not username:
            username = input("请输入用户名: ").strip()
        if not password:
            password = input("请输入密码: ").strip()
        
        # 验证用户名密码不能为空
        if not username or not password:
            print("错误: 用户名和密码不能为空")
            return False
        
        # 缓存用户名密码用于自动重新登录（仅在内存中，不保存到文件）
        self._cached_username = username
        self._cached_password = password
        
        # 生成身份标识
        new_identity = hashlib.md5((username + password).encode()).hexdigest()
        
        # 如果已登录且身份相同，直接返回
        if self.is_login() and self.identity == new_identity:
            print("已登录，无需重复登录")
            return True
        
        # 获取登录参数
        if not self.get_login_param():
            return False
        
        # RSA加密用户名和密码
        rsa_username = self.rsa_encrypt(self.login_param['jRsaKey'], username)
        rsa_password = self.rsa_encrypt(self.login_param['jRsaKey'], password)
        
        if not rsa_username or not rsa_password:
            print("加密用户名密码失败")
            return False
        
        # 添加前缀
        pre = self.login_param['pre']
        rsa_username = pre + rsa_username
        rsa_password = pre + rsa_password
        
        # 登录请求
        login_url = f"{AUTH_URL}/api/logbox/oauth2/loginSubmit.do"
        login_data = {
            'appKey': APP_ID,
            'accountType': ACCOUNT_TYPE,
            'userName': rsa_username,
            'password': rsa_password,
            'validateCode': captcha or '',
            'captchaToken': self.login_param.get('captchaToken', ''),
            'returnUrl': RETURN_URL,
            'dynamicCheck': 'FALSE',
            'clientType': CLIENT_TYPE,
            'cb_SaveName': '1',
            'isOauth2': 'false',
            'state': '',
            'paramId': self.login_param['paramId']
        }
        
        headers = {
            'REQID': self.login_param['reqId'],
            'lt': self.login_param['lt']
        }
        
        if self.enable_debug:
            self.logger.debug(f"登录请求URL: {login_url}")
            # 隐藏敏感信息
            safe_data = login_data.copy()
            safe_data['userName'] = f"***[长度:{len(login_data['userName'])}]"
            safe_data['password'] = f"***[长度:{len(login_data['password'])}]"
            self.logger.debug(f"登录请求数据: {safe_data}")
            self.logger.debug(f"登录请求头: {headers}")
        
        try:
            response = self.session.post(login_url, data=login_data, headers=headers)
            if self.enable_debug:
                self.logger.debug(f"登录响应状态码: {response.status_code}")
                self.logger.debug(f"登录响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                if result.get('result') == 0:
                    # 登录成功，获取重定向URL
                    to_url = result.get('toUrl')
                    if to_url:
                        # 获取Session
                        session_url = f"{API_URL}/getSessionForPC.action"
                        session_params = self.client_suffix()
                        session_params['redirectURL'] = to_url
                        
                        if self.enable_debug:
                            self.logger.debug(f"获取Session URL: {session_url}")
                            self.logger.debug(f"获取Session 参数: {session_params}")
                        
                        session_response = self.session.post(session_url, params=session_params)
                        if self.enable_debug:
                            self.logger.debug(f"Session响应状态码: {session_response.status_code}")
                            self.logger.debug(f"Session响应内容: {session_response.text}")
                        
                        if session_response.status_code == 200:
                            session_result = session_response.json()
                            # 检查不同的响应字段
                            res_code = session_result.get('resCode') or session_result.get('res_code')
                            if res_code == 0:
                                self.token_info = session_result
                                self.identity = new_identity
                                self.save_cache()
                                print(f"登录成功: {self.token_info.get('loginName', username)}")
                                return True
                            else:
                                res_message = session_result.get('resMessage') or session_result.get('res_message')
                                print(f"获取session失败: {res_message}")
                else:
                    print(f"登录失败: {result.get('msg', '未知错误')}")
                    # 如果需要验证码，提示用户输入
                    if '验证码' in result.get('msg', ''):
                        captcha = input("请输入验证码: ")
                        return self.login(username, password, captcha)
        except Exception as e:
            print(f"登录请求失败: {e}")
        
        return False
    
    def refresh_session(self) -> bool:
        """刷新session"""
        if not self.token_info or 'accessToken' not in self.token_info:
            print("没有有效的accessToken，尝试重新登录")
            return self.auto_login()
            
        url = f"{API_URL}/getSessionForPC.action"
        params = self.client_suffix()
        params.update({
            'appId': APP_ID,
            'accessToken': self.token_info['accessToken']
        })
        
        headers = {
            'X-Request-ID': str(uuid.uuid4())
        }
        
        if self.enable_debug:
            self.logger.debug(f"刷新Session URL: {url}")
            self.logger.debug(f"刷新Session 参数: {params}")
            self.logger.debug(f"刷新Session 请求头: {headers}")
        
        try:
            response = self.session.get(url, params=params, headers=headers)
            if self.enable_debug:
                self.logger.debug(f"刷新Session响应状态码: {response.status_code}")
                self.logger.debug(f"刷新Session响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                if result.get('res_code') == 0:
                    # 更新token信息
                    self.token_info.update(result)
                    self.save_cache()
                    print("Session刷新成功")
                    return True
                else:
                    print(f"刷新session失败: {result.get('res_message')}")
                    # 如果token无效，清理缓存并尝试重新登录
                    if result.get('resCode') == 'UserInvalidOpenToken':
                        print("Token已失效，清理缓存并重新登录")
                        self.clear_cache()
                    return self.auto_login()
            else:
                print(f"Session刷新请求失败: {response.status_code}")
                return self.auto_login()
        except Exception as e:
            print(f"刷新session失败: {e}")
            return self.auto_login()
    
    def get_family_id(self) -> str:
        """获取家庭云ID"""
        if self.family_id:
            return self.family_id
        
        url = f"{API_URL}/family/manage/getFamilyList.action"
        headers = self.signature_header(url, "GET")
        params = self.client_suffix()
        
        if self.enable_debug:
            self.logger.debug(f"获取家庭云ID URL: {url}")
            self.logger.debug(f"获取家庭云ID 参数: {params}")
            self.logger.debug(f"获取家庭云ID 请求头: {headers}")
        
        try:
            response = self.session.get(url, headers=headers, params=params)
            if self.enable_debug:
                self.logger.debug(f"获取家庭云ID响应状态码: {response.status_code}")
                self.logger.debug(f"获取家庭云ID响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                family_list = result.get('familyInfoResp', [])
                if family_list:
                    self.family_id = str(family_list[0]['familyId'])
                    return self.family_id
        except Exception as e:
            print(f"获取家庭云ID失败: {e}")
        return ""